#include "DndDataTreeModel.h"

#include <Xml/XmlDirectory.h>
#include <Xml/XmlPath.h>
#include <QDir>

#include <QDebug>

class DndDataTreeNode
{
public:
    DndDataTreeNode() : parent(0) {}
    ~DndDataTreeNode();
    DndDataTreeNode *parent;
    int row;
    QString dirEntry;
    QList<DndDataTreeNode *> subTree;
    QMap<QString, DndDataTreeNode *> subMap;
};

DndDataTreeNode::~DndDataTreeNode()
{
    qDeleteAll(subTree);
}

DndDataTreeModel::DndDataTreeModel()
{
    iconMap.insert("Equipment", QIcon(":/icons/equipment"));
    iconMap.insert("Character", QIcon(":/icons/character"));
    iconMap.insert("Trait", QIcon(":/icons/trait"));
    iconMap.insert("Feat", QIcon(":/icons/feat"));
    iconMap.insert("Class", QIcon(":/icons/class"));
    iconMap.insert("Power", QIcon(":/icons/power"));
    iconMap.insert("Skill", QIcon(":/icons/skill"));
    iconMap.insert("Race", QIcon(":/icons/race"));
    iconMap.insert("d10", QIcon(":/icons/d10"));
}

DndDataTreeModel::~DndDataTreeModel()
{
    qDeleteAll(mTree);
}

QModelIndex DndDataTreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (column > 0)
        return QModelIndex();
    if (parent.isValid())
    {
        DndDataTreeNode *node = static_cast<DndDataTreeNode*>(parent.internalPointer());
        return createIndex(row, 0, node->subTree[row]);
    }
    return createIndex(row, 0, mTree[row]);
}

QModelIndex DndDataTreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();
    DndDataTreeNode *node = static_cast<DndDataTreeNode*>(index.internalPointer());
    if (node->parent)
        return createIndex(node->parent->row, 0, node->parent);
    return QModelIndex();
}

bool DndDataTreeModel::hasChildren(const QModelIndex &parent) const
{
    return rowCount(parent) > 0;
}

int DndDataTreeModel::rowCount(const QModelIndex &parent) const
{
    if (!parent.isValid())
        return mTree.count();
    DndDataTreeNode *node = static_cast<DndDataTreeNode*>(parent.internalPointer());
    return node->subTree.count();
}

int DndDataTreeModel::columnCount (const QModelIndex &parent) const
{
    if (rowCount(parent) > 0)
        return 1;
    return 0;
}

QVariant DndDataTreeModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();
    DndDataTreeNode *node = static_cast<DndDataTreeNode*>(index.internalPointer());
    QString name = node->dirEntry;
    if (role == Qt::DisplayRole)
        return name;
    if (role == Qt::DecorationRole)
    {
        if (iconMap.contains(name))
            return iconMap[name];
        else
            return iconMap["d10"];
    }
    return QVariant();
}

DndDataTreeModel &DndDataTreeModel::getInstance()
{
    static DndDataTreeModel instance;
    return instance;
}

void DndDataTreeModel::directoryPathUpdated(const QString &path)
{
    QStringList parts = path.split("/");
    Q_ASSERT(parts.count() > 1);
    QString typeName = parts[0];
    Q_ASSERT(mMap.contains(typeName));
    DndDataTreeNode *typeNode = mMap[typeName];
    int row = mTree.indexOf(typeNode);
    QModelIndex parent = index(row, 0, QModelIndex());
    int part = 1;
    QString nodeName;
    DndDataTreeNode *node = typeNode;
    while (true)
    {
        nodeName = parts[part];
        if (part == parts.count() - 1)
            break;
        Q_ASSERT(node->subMap.contains(nodeName));
        node = node->subMap[nodeName];
        int ix = typeNode->subTree.indexOf(node);
        parent = index(ix, 0, parent);
        part += 1;
    }
    if (node->subMap.contains(nodeName)) // is a removal
    {
        DndDataTreeNode *subNode = typeNode->subMap[nodeName];
        int ix = subNode->row;
        beginRemoveRows(parent, ix, ix);
        node->subMap.remove(nodeName);
        node->subTree.removeAt(ix);
        delete subNode;
        for (int i = ix; i < node->subTree.count(); ++i)
            node->subTree[i]->row = i;
        endRemoveRows();
    }
    else  // is an addition
    {
        int ix = typeNode->subTree.count();
        DndDataTreeNode *subNode = new DndDataTreeNode;
        subNode->parent = node;
        subNode->dirEntry = nodeName;
        subNode->row = ix;
        beginInsertRows(parent, ix, ix);
        node->subMap.insert(nodeName, subNode);
        node->subTree.append(subNode);
        endInsertRows();
    }
}

void DndDataTreeModel::setUpDetailTree(DndDataTreeNode *node, const XmlPath &xp)
{
    XmlDirectory xmlDir;
    QDir subTypeDir = xmlDir.dataSubDir(xp);
    QStringList entries = subTypeDir.entryList(QStringList(), QDir::Files);
    if (entries.count() == 0)
        return;
    QString subtypeName = XmlPath::SUB_TYPE_NAMES[xp.subType()];
    DndDataTreeNode *subtypeNode = new DndDataTreeNode;
    subtypeNode->dirEntry = subtypeName;
    subtypeNode->parent = node;
    subtypeNode->row = xp.subType();
    node->subTree.append(subtypeNode);
    node->subMap.insert(subtypeName, subtypeNode);
    QStringList::const_iterator it = entries.begin();
    for (int row = 0; it != entries.end(); ++it, ++row)
    {
        QString name = *it;
        DndDataTreeNode *nameNode = new DndDataTreeNode;
        nameNode->parent = subtypeNode;
        nameNode->dirEntry = name;
        nameNode->row = row;
        subtypeNode->subTree.append(nameNode);
        subtypeNode->subMap.insert(name, nameNode);
    }
}

void DndDataTreeModel::setUpTypeTree(DndDataTreeNode *node, XmlPath::BaseTypes type)
{
    XmlDirectory xmlDir;
    QDir typeDir = xmlDir.dataDir(type);
    QStringList entries = typeDir.entryList(QStringList(), QDir::Files);
    QStringList::const_iterator it = entries.begin();
    for (int row = 0; it != entries.end(); ++it, ++row)
    {
        QString name = *it;
        DndDataTreeNode *nameNode = new DndDataTreeNode;
        nameNode->parent = node;
        nameNode->row = row;
        nameNode->dirEntry = name;
        node->subTree.append(nameNode);
        node->subMap.insert(name, nameNode);
        if (type == XmlPath::CHARACTER_BASE && type == XmlPath::SKILL_BASE)
            continue;
        XmlPath xp(type, name);
        xp.setSubType(XmlPath::POWER_TYPE);
        setUpDetailTree(nameNode, xp);
        xp.setSubType(XmlPath::TRAIT_TYPE);
        setUpDetailTree(nameNode, xp);
    }
}

void DndDataTreeModel::dumpTree(QList<DndDataTreeNode*> tree, int indent)
{
    QString indentStr = QString("   ").repeated(indent);
    for (int i = 0; i < tree.count(); ++i)
    {
        qDebug("%s%s", indentStr.toLocal8Bit().constData(), tree[i]->dirEntry.toLocal8Bit().constData());
        dumpTree(tree[i]->subTree, indent + 1);
    }
}

void DndDataTreeModel::initialize()
{
    qDebug() << ">>> DndDataTreeModel::initialize()";
    mMap.clear();
    mTree.clear();
    for (int i = 0; i < XmlPath::NUM_BASE_TYPES; ++i)
    {
        XmlPath::BaseTypes type = static_cast<XmlPath::BaseTypes>(i);
        DndDataTreeNode *node = new DndDataTreeNode;
        node->parent = 0;
        node->row = i;
        node->dirEntry = XmlPath::BASE_TYPE_NAMES[type];
        setUpTypeTree(node, type);
        mTree.append(node);
        mMap.insert(node->dirEntry, node);
    }
    dumpTree(mTree, 1);
    qDebug() << "<<< DndDataTreeModel::initialize()";
}
